//  (C) Copyright John Maddock 2007.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifdef _MSC_VER
#define _SCL_SECURE_NO_WARNINGS
#endif

#include <boost/detail/lightweight_test.hpp>
#include <boost/math/special_functions/round.hpp>
#include <boost/math/special_functions/trunc.hpp>
#include <boost/math/special_functions/modf.hpp>
#include <boost/math/special_functions/sign.hpp>
#include <boost/random/mersenne_twister.hpp>
#include "test.hpp"

#if !defined(TEST_MPF_50) && !defined(TEST_MPF) && !defined(TEST_BACKEND) && !defined(TEST_CPP_DEC_FLOAT) && \
    !defined(TEST_MPFR) && !defined(TEST_MPFR_50) && !defined(TEST_MPFI_50) && !defined(TEST_FLOAT128) &&    \
    !defined(TEST_CPP_BIN_FLOAT)
#define TEST_MPF_50
#define TEST_MPFR_50
#define TEST_MPFI_50
#define TEST_BACKEND
#define TEST_CPP_DEC_FLOAT
#define TEST_FLOAT128
#define TEST_CPP_BIN_FLOAT

#ifdef _MSC_VER
#pragma message("CAUTION!!: No backend type specified so testing everything.... this will take some time!!")
#endif
#ifdef __GNUC__
#pragma warning "CAUTION!!: No backend type specified so testing everything.... this will take some time!!"
#endif

#endif

#if defined(TEST_MPF_50)
#include <nil/crypto3/multiprecision/gmp.hpp>
#endif
#ifdef TEST_MPFR_50
#include <nil/crypto3/multiprecision/mpfr.hpp>
#endif
#ifdef TEST_MPFI_50
#include <nil/crypto3/multiprecision/mpfi.hpp>
#endif
#ifdef TEST_BACKEND
#include <nil/crypto3/multiprecision/concepts/mp_number_archetypes.hpp>
#endif
#ifdef TEST_CPP_DEC_FLOAT
#include <nil/crypto3/multiprecision/cpp_dec_float.hpp>
#endif
#ifdef TEST_CPP_BIN_FLOAT
#include <nil/crypto3/multiprecision/cpp_bin_float.hpp>
#endif
#ifdef TEST_FLOAT128
#include <nil/crypto3/multiprecision/float128.hpp>
#endif

#ifdef BOOST_MSVC
#pragma warning(disable : 4127)
#endif

boost::mt19937 rng;

template<class T>
T get_random() {
    //
    // Fill all the bits in T with random values,
    // likewise set the exponent to a random value
    // that will still fit inside a T, and always
    // have a remainder as well as an integer part.
    //
    int bits = boost::math::tools::digits<T>();
    int shift = 0;
    int exponent = rng() % (bits - 4);
    T result = 0;
    while (bits > 0) {
        result += ldexp(static_cast<T>(rng()), shift);
        shift += std::numeric_limits<int>::digits;
        bits -= std::numeric_limits<int>::digits;
    }
    return rng() & 1u ? T(-ldexp(frexp(result, &bits), exponent)) : T(ldexp(frexp(result, &bits), exponent));
}

template<class T, class U>
typename boost::disable_if_c<nil::crypto3::multiprecision::is_interval_number<T>::value>::type check_within_half(T a,
                                                                                                                 U u) {
    BOOST_MATH_STD_USING
    if (fabs(a - u) > 0.5f) {
        BOOST_ERROR("Rounded result differed by more than 0.5 from the original");
        std::cerr << "Values were: " << std::setprecision(35) << std::setw(40) << std::left << a << u << std::endl;
    }
    if ((fabs(a - u) == 0.5f) && (fabs(static_cast<T>(u)) < fabs(a))) {
        BOOST_ERROR("Rounded result was towards zero with boost::round");
        std::cerr << "Values were: " << std::setprecision(35) << std::setw(40) << std::left << a << u << std::endl;
    }
}
template<class T, class U>
typename boost::enable_if_c<nil::crypto3::multiprecision::is_interval_number<T>::value>::type check_within_half(T a,
                                                                                                                U u) {
    BOOST_MATH_STD_USING
    if (upper(T(fabs(a - u))) > 0.5f) {
        BOOST_ERROR("Rounded result differed by more than 0.5 from the original");
        std::cerr << "Values were: " << std::setprecision(35) << std::setw(40) << std::left << a << u << std::endl;
    }
    if ((upper(T(fabs(a - u))) == 0.5f) && (fabs(static_cast<T>(u)) < fabs(a))) {
        BOOST_ERROR("Rounded result was towards zero with boost::round");
        std::cerr << "Values were: " << std::setprecision(35) << std::setw(40) << std::left << a << u << std::endl;
    }
}

//
// We may not have an abs overload for long long so provide a fall back:
//
inline unsigned safe_abs(int const& v) {
    return v < 0 ? static_cast<unsigned>(1u) + static_cast<unsigned>(-(v + 1)) : v;
}
inline unsigned long safe_abs(long const& v) {
    return v < 0 ? static_cast<unsigned long>(1u) + static_cast<unsigned long>(-(v + 1)) : v;
}
inline unsigned long long safe_abs(long long const& v) {
    return v < 0 ? static_cast<unsigned long long>(1u) + static_cast<unsigned long long>(-(v + 1)) : v;
}
template<class T>
inline typename boost::disable_if_c<boost::is_integral<T>::value, T>::type safe_abs(T const& v) {
    return v < 0 ? -v : v;
}

template<class T, class U>
void check_trunc_result(T a, U u) {
    BOOST_MATH_STD_USING
    if (fabs(a - u) >= 1) {
        BOOST_ERROR("Rounded result differed by more than 1 from the original");
        std::cerr << "Values were: " << std::setprecision(35) << std::setw(40) << std::left << a << u << std::endl;
    }
    if (abs(a) < safe_abs(u)) {
        BOOST_ERROR("Truncated result had larger absolute value than the original");
        std::cerr << "Values were: " << std::setprecision(35) << std::setw(40) << std::left << a << u << std::endl;
    }
    if (fabs(static_cast<T>(u)) > fabs(a)) {
        BOOST_ERROR("Rounded result was away from zero with boost::trunc");
        std::cerr << "Values were: " << std::setprecision(35) << std::setw(40) << std::left << a << u << std::endl;
    }
}

template<class T, class U>
void check_modf_result(T a, T fract, U ipart) {
    BOOST_MATH_STD_USING
    if (fract + ipart != a) {
        BOOST_ERROR("Fractional and integer results do not add up to the original value");
        std::cerr << "Values were: " << std::setprecision(35) << " " << std::left << a << ipart << " " << fract
                  << std::endl;
    }
    if ((boost::math::sign(a) != boost::math::sign(fract)) && boost::math::sign(fract)) {
        BOOST_ERROR("Original and fractional parts have differing signs");
        std::cerr << "Values were: " << std::setprecision(35) << " " << std::left << a << ipart << " " << fract
                  << std::endl;
    }
    if ((boost::math::sign(a) != boost::math::sign(ipart)) && boost::math::sign(ipart)) {
        BOOST_ERROR("Original and integer parts have differing signs");
        std::cerr << "Values were: " << std::setprecision(35) << " " << std::left << a << ipart << " " << ipart
                  << std::endl;
    }
    if (fabs(a - ipart) >= 1) {
        BOOST_ERROR("Rounded result differed by more than 1 from the original");
        std::cerr << "Values were: " << std::setprecision(35) << std::setw(40) << std::left << a << ipart << std::endl;
    }
}

template<class T>
void test() {
    BOOST_MATH_STD_USING

    for (int i = 0; i < 1000; ++i) {
        T arg = get_random<T>();
        T r = round(arg);
        check_within_half(arg, r);
        BOOST_TEST(r == round(arg + 0));
        r = trunc(arg);
        check_trunc_result(arg, r);
        BOOST_TEST(r == trunc(arg + 0));
        T frac = modf(arg, &r);
        check_modf_result(arg, frac, r);

        if (abs(r) < (std::numeric_limits<int>::max)()) {
            int i = iround(arg);
            check_within_half(arg, i);
            BOOST_TEST(i == iround(arg + 0));
            i = itrunc(arg);
            check_trunc_result(arg, i);
            BOOST_TEST(i == itrunc(arg + 0));
            r = modf(arg, &i);
            check_modf_result(arg, r, i);
        }
        if (abs(r) < (std::numeric_limits<long>::max)()) {
            long l = lround(arg);
            check_within_half(arg, l);
            BOOST_TEST(l == lround(arg + 0));
            l = ltrunc(arg);
            check_trunc_result(arg, l);
            BOOST_TEST(l == ltrunc(arg + 0));
            r = modf(arg, &l);
            check_modf_result(arg, r, l);
        }

#ifdef BOOST_HAS_LONG_LONG
        if (abs(r) < (std::numeric_limits<boost::long_long_type>::max)()) {
            boost::long_long_type ll = llround(arg);
            check_within_half(arg, ll);
            BOOST_TEST(ll == llround(arg + 0));
            ll = lltrunc(arg);
            check_trunc_result(arg, ll);
            BOOST_TEST(ll == lltrunc(arg + 0));
            r = modf(arg, &ll);
            check_modf_result(arg, r, ll);
        }
#endif
    }
    //
    // Test boundary cases:
    //
    if (std::numeric_limits<T>::digits >= std::numeric_limits<int>::digits) {
        int si = iround(static_cast<T>((std::numeric_limits<int>::max)()));
        check_within_half(static_cast<T>((std::numeric_limits<int>::max)()), si);
        BOOST_TEST(si == iround(static_cast<T>((std::numeric_limits<int>::max)()) + 0));
        si = iround(static_cast<T>((std::numeric_limits<int>::min)()));
        check_within_half(static_cast<T>((std::numeric_limits<int>::min)()), si);
        BOOST_TEST(si == iround(static_cast<T>((std::numeric_limits<int>::min)()) + 0));
        si = itrunc(static_cast<T>((std::numeric_limits<int>::max)()));
        check_trunc_result(static_cast<T>((std::numeric_limits<int>::max)()), si);
        BOOST_TEST(si == itrunc(static_cast<T>((std::numeric_limits<int>::max)()) + 0));
        si = itrunc(static_cast<T>((std::numeric_limits<int>::min)()));
        check_trunc_result(static_cast<T>((std::numeric_limits<int>::min)()), si);
        BOOST_TEST(si == itrunc(static_cast<T>((std::numeric_limits<int>::min)()) + 0));

        si = iround(static_cast<T>((std::numeric_limits<int>::max)() - 1));
        check_within_half(static_cast<T>((std::numeric_limits<int>::max)() - 1), si);
        si = iround(static_cast<T>((std::numeric_limits<int>::min)() + 1));
        check_within_half(static_cast<T>((std::numeric_limits<int>::min)() + 1), si);
        si = itrunc(static_cast<T>((std::numeric_limits<int>::max)() - 1));
        check_trunc_result(static_cast<T>((std::numeric_limits<int>::max)() - 1), si);
        si = itrunc(static_cast<T>((std::numeric_limits<int>::min)() + 1));
        check_trunc_result(static_cast<T>((std::numeric_limits<int>::min)() + 1), si);
    }
    if (std::numeric_limits<T>::digits >= std::numeric_limits<long>::digits) {
        long k = lround(static_cast<T>((std::numeric_limits<long>::max)()));
        check_within_half(static_cast<T>((std::numeric_limits<long>::max)()), k);
        BOOST_TEST(k == lround(static_cast<T>((std::numeric_limits<long>::max)()) + 0));
        k = lround(static_cast<T>((std::numeric_limits<long>::min)()));
        check_within_half(static_cast<T>((std::numeric_limits<long>::min)()), k);
        BOOST_TEST(k == lround(static_cast<T>((std::numeric_limits<long>::min)()) + 0));
        k = ltrunc(static_cast<T>((std::numeric_limits<long>::max)()));
        check_trunc_result(static_cast<T>((std::numeric_limits<long>::max)()), k);
        BOOST_TEST(k == ltrunc(static_cast<T>((std::numeric_limits<long>::max)()) + 0));
        k = ltrunc(static_cast<T>((std::numeric_limits<long>::min)()));
        check_trunc_result(static_cast<T>((std::numeric_limits<long>::min)()), k);
        BOOST_TEST(k == ltrunc(static_cast<T>((std::numeric_limits<long>::min)()) + 0));

        k = lround(static_cast<T>((std::numeric_limits<long>::max)() - 1));
        check_within_half(static_cast<T>((std::numeric_limits<long>::max)() - 1), k);
        k = lround(static_cast<T>((std::numeric_limits<long>::min)() + 1));
        check_within_half(static_cast<T>((std::numeric_limits<long>::min)() + 1), k);
        k = ltrunc(static_cast<T>((std::numeric_limits<long>::max)() - 1));
        check_trunc_result(static_cast<T>((std::numeric_limits<long>::max)() - 1), k);
        k = ltrunc(static_cast<T>((std::numeric_limits<long>::min)() + 1));
        check_trunc_result(static_cast<T>((std::numeric_limits<long>::min)() + 1), k);
    }
#ifndef BOOST_NO_LONG_LONG
    if (std::numeric_limits<T>::digits >= std::numeric_limits<boost::long_long_type>::digits) {
        boost::long_long_type j = llround(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)()));
        check_within_half(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)()), j);
        BOOST_TEST(j == llround(static_cast<T>((std::numeric_limits<long long>::max)()) + 0));
        j = llround(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)()));
        check_within_half(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)()), j);
        BOOST_TEST(j == llround(static_cast<T>((std::numeric_limits<long long>::min)()) + 0));
        j = lltrunc(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)()));
        check_trunc_result(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)()), j);
        BOOST_TEST(j == lltrunc(static_cast<T>((std::numeric_limits<long long>::max)()) + 0));
        j = lltrunc(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)()));
        check_trunc_result(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)()), j);
        BOOST_TEST(j == lltrunc(static_cast<T>((std::numeric_limits<long long>::min)()) + 0));

        j = llround(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)() - 1));
        check_within_half(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)() - 1), j);
        j = llround(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)() + 1));
        check_within_half(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)() + 1), j);
        j = lltrunc(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)() - 1));
        check_trunc_result(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)() - 1), j);
        j = lltrunc(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)() + 1));
        check_trunc_result(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)() + 1), j);
    }
#endif
    //
    // Finish off by testing the error handlers:
    //
    T result;
#ifndef BOOST_NO_EXCEPTIONS
    BOOST_CHECK_THROW(result = static_cast<T>(iround(static_cast<T>(1e20))), boost::math::rounding_error);
    BOOST_CHECK_THROW(result = static_cast<T>(iround(static_cast<T>(-1e20))), boost::math::rounding_error);
    BOOST_CHECK_THROW(result = static_cast<T>(lround(static_cast<T>(1e20))), boost::math::rounding_error);
    BOOST_CHECK_THROW(result = static_cast<T>(lround(static_cast<T>(-1e20))), boost::math::rounding_error);
#ifdef BOOST_HAS_LONG_LONG
    BOOST_CHECK_THROW(result = static_cast<T>(llround(static_cast<T>(1e20))), boost::math::rounding_error);
    BOOST_CHECK_THROW(result = static_cast<T>(llround(static_cast<T>(-1e20))), boost::math::rounding_error);
#endif
    if (std::numeric_limits<T>::has_infinity) {
        BOOST_CHECK_EQUAL(static_cast<T>(round(std::numeric_limits<T>::infinity())),
                          std::numeric_limits<T>::infinity());    // See C99 Annex F.
        BOOST_CHECK_THROW(result = static_cast<T>(iround(std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(iround(-std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(lround(std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(lround(-std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
#ifdef BOOST_HAS_LONG_LONG
        BOOST_CHECK_THROW(result = static_cast<T>(llround(std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(llround(-std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
#endif
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        BOOST_CHECK((nil::crypto3::multiprecision::isnan)(round(std::numeric_limits<T>::quiet_NaN())));
        BOOST_CHECK_THROW(result = static_cast<T>(iround(std::numeric_limits<T>::quiet_NaN())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(lround(std::numeric_limits<T>::quiet_NaN())),
                          boost::math::rounding_error);
#ifdef BOOST_HAS_LONG_LONG
        BOOST_CHECK_THROW(result = static_cast<T>(llround(std::numeric_limits<T>::quiet_NaN())),
                          boost::math::rounding_error);
#endif
    }
    BOOST_CHECK_THROW(result = static_cast<T>(itrunc(static_cast<T>(1e20))), boost::math::rounding_error);
    BOOST_CHECK_THROW(result = static_cast<T>(itrunc(static_cast<T>(-1e20))), boost::math::rounding_error);
    BOOST_CHECK_THROW(result = static_cast<T>(ltrunc(static_cast<T>(1e20))), boost::math::rounding_error);
    BOOST_CHECK_THROW(result = static_cast<T>(ltrunc(static_cast<T>(-1e20))), boost::math::rounding_error);
#ifdef BOOST_HAS_LONG_LONG
    BOOST_CHECK_THROW(result = static_cast<T>(lltrunc(static_cast<T>(1e20))), boost::math::rounding_error);
    BOOST_CHECK_THROW(result = static_cast<T>(lltrunc(static_cast<T>(-1e20))), boost::math::rounding_error);
#endif
    if (std::numeric_limits<T>::has_infinity) {
        BOOST_CHECK_EQUAL(static_cast<T>(trunc(std::numeric_limits<T>::infinity())),
                          std::numeric_limits<T>::infinity());
        BOOST_CHECK_EQUAL(static_cast<T>(trunc(-std::numeric_limits<T>::infinity())),
                          -std::numeric_limits<T>::infinity());
        BOOST_CHECK_THROW(result = static_cast<T>(itrunc(std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(itrunc(-std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(ltrunc(std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(ltrunc(-std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
#ifdef BOOST_HAS_LONG_LONG
        BOOST_CHECK_THROW(result = static_cast<T>(lltrunc(std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(lltrunc(-std::numeric_limits<T>::infinity())),
                          boost::math::rounding_error);
#endif
    }
    if (std::numeric_limits<T>::has_quiet_NaN) {
        BOOST_CHECK((nil::crypto3::multiprecision::isnan)(trunc(std::numeric_limits<T>::quiet_NaN())));
        BOOST_CHECK_THROW(result = static_cast<T>(itrunc(std::numeric_limits<T>::quiet_NaN())),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(ltrunc(std::numeric_limits<T>::quiet_NaN())),
                          boost::math::rounding_error);
#ifdef BOOST_HAS_LONG_LONG
        BOOST_CHECK_THROW(result = static_cast<T>(lltrunc(std::numeric_limits<T>::quiet_NaN())),
                          boost::math::rounding_error);
#endif
    }
    if (std::numeric_limits<T>::digits >= std::numeric_limits<int>::digits) {
        BOOST_CHECK_THROW(result = static_cast<T>(itrunc(static_cast<T>((std::numeric_limits<int>::max)()) + 1)),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(itrunc(static_cast<T>((std::numeric_limits<int>::min)()) - 1)),
                          boost::math::rounding_error);
    }
    if (std::numeric_limits<T>::digits >= std::numeric_limits<long>::digits) {
        BOOST_CHECK_THROW(result = static_cast<T>(ltrunc(static_cast<T>((std::numeric_limits<long>::max)()) + 1)),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(ltrunc(static_cast<T>((std::numeric_limits<long>::min)()) - 1)),
                          boost::math::rounding_error);
    }
#ifndef BOOST_NO_LONG_LONG
    if (std::numeric_limits<T>::digits >= std::numeric_limits<boost::long_long_type>::digits) {
        BOOST_CHECK_THROW(
            result = static_cast<T>(lltrunc(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)()) + 1)),
            boost::math::rounding_error);
        BOOST_CHECK_THROW(
            result = static_cast<T>(lltrunc(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)()) - 1)),
            boost::math::rounding_error);
    }
#endif
    if (std::numeric_limits<T>::digits >= std::numeric_limits<int>::digits) {
        BOOST_CHECK_THROW(result = static_cast<T>(iround(static_cast<T>((std::numeric_limits<int>::max)()) + 1)),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(iround(static_cast<T>((std::numeric_limits<int>::min)()) - 1)),
                          boost::math::rounding_error);
    }
    if (std::numeric_limits<T>::digits >= std::numeric_limits<long>::digits) {
        BOOST_CHECK_THROW(result = static_cast<T>(lround(static_cast<T>((std::numeric_limits<long>::max)()) + 1)),
                          boost::math::rounding_error);
        BOOST_CHECK_THROW(result = static_cast<T>(lround(static_cast<T>((std::numeric_limits<long>::min)()) - 1)),
                          boost::math::rounding_error);
    }
#ifndef BOOST_NO_LONG_LONG
    if (std::numeric_limits<T>::digits >= std::numeric_limits<boost::long_long_type>::digits) {
        BOOST_CHECK_THROW(
            result = static_cast<T>(llround(static_cast<T>((std::numeric_limits<boost::long_long_type>::max)()) + 1)),
            boost::math::rounding_error);
        BOOST_CHECK_THROW(
            result = static_cast<T>(llround(static_cast<T>((std::numeric_limits<boost::long_long_type>::min)()) - 1)),
            boost::math::rounding_error);
    }
#endif
#endif
}

int main() {
#ifdef TEST_MPF_50
    test<nil::crypto3::multiprecision::mpf_float_50>();
    test<nil::crypto3::multiprecision::mpf_float_100>();
#endif
#ifdef TEST_MPFR_50
    test<nil::crypto3::multiprecision::mpfr_float_50>();
    test<nil::crypto3::multiprecision::mpfr_float_100>();
#endif
#ifdef TEST_MPFI_50
    test<nil::crypto3::multiprecision::mpfi_float_50>();
    test<nil::crypto3::multiprecision::mpfi_float_100>();
#endif
#ifdef TEST_CPP_DEC_FLOAT
    test<nil::crypto3::multiprecision::cpp_dec_float_50>();
    test<nil::crypto3::multiprecision::cpp_dec_float_100>();
#ifndef SLOW_COMPLER
    // Some "peculiar" digit counts which stress our code:
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<65>>>();
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<64>>>();
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<63>>>();
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<62>>>();
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<61, long long>>>();
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_dec_float<60, long long>>>();
    test<nil::crypto3::multiprecision::number<
        nil::crypto3::multiprecision::cpp_dec_float<59, long long, std::allocator<char>>>>();
    test<nil::crypto3::multiprecision::number<
        nil::crypto3::multiprecision::cpp_dec_float<58, long long, std::allocator<char>>>>();
#endif
#endif
#ifdef TEST_CPP_BIN_FLOAT
    test<nil::crypto3::multiprecision::cpp_bin_float_50>();
    test<nil::crypto3::multiprecision::cpp_bin_float_100>();
    test<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_bin_float<
        35, nil::crypto3::multiprecision::digit_base_10, std::allocator<char>, boost::long_long_type>>>();
#endif
#ifdef TEST_BACKEND
    test<
        nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::concepts::number_backend_float_architype>>();
#endif
#ifdef TEST_FLOAT128
    test<nil::crypto3::multiprecision::float128>();
#endif
    return boost::report_errors();
}
